<?php

/**
 * 
 * @desc This class provides connection to data base, query preparing and fetching
 * @author Barbushin Sergey http://www.linkedin.com/in/barbushin
 * 
 */
class DaBase_Db {
	
	protected $dbLink;
	protected $dbName;
	protected $router;
	protected $transactionStarted;
	protected $debugCallback;
	
	const PRE_VALUE = '?';
	const PRE_VALUES = ',?';
	const PRE_NAME = '#';
	const PRE_NAMES = ',#';
	const PRE_EQUALS = ',=';
	const PRE_AS_IS = '$';

	public function __construct($host, $login, $password, $dbName = null, $persistent = false, DaBase_Router $router = null) {
		$this->dbLink = $this->connect($host, $login, $password, $persistent);
		
		if ($dbName) {
			$this->selectDb($dbName);
		}
		
		if(!$router) {
			$router = new DaBase_Router();
		}
		$this->router = $router;
	}
	
	public function getRouter() {
		return $this->router;
	}

	protected function connect($host, $login, $password, $persistent) {
		$dbLink = $persistent ? @mysql_pconnect($host, $login, $password) : @mysql_connect($host, $login, $password);
		if (!$dbLink) {
			throw new DaBase_Exception('Could not connect: ' . mysql_error());
		}
		return $dbLink;
	}

	protected function selectDb($dbName) {
		if (!mysql_selectdb($dbName, $this->dbLink)) {
			throw new DaBase_Exception('Could not select DB: ' . mysql_error($this->dbLink));
		}
		$this->dbName = $dbName;
	}

	public function query($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->exec($this->prepareSql($prepareSql, $replacers));
	}

	public function exec($preparedSql) {
		$this->debugSql($preparedSql);
		$result = mysql_query($preparedSql, $this->dbLink);
		if (!$result) {
			throw new DaBase_Exception('SQL: ' . $preparedSql . ' FAILED WITH ERROR: ' . mysql_error($this->dbLink));
		}
		return $result;
	}

	public function fetchPreparedSql($preparedSql, $oneColumn = false, $oneRow = false) {
		$fetched = $oneColumn && $oneRow ? null : array();
		$result = $this->exec($preparedSql);
		if (mysql_num_rows($result)) {
			while ($row = mysql_fetch_assoc($result)) {
				if ($oneColumn) {
					if ($oneRow) {
						return reset($row);
					}
					$fetched[] = reset($row);
				}
				elseif ($oneRow) {
					return $row;
				}
				else {
					$fetched[] = $row;
				}
			}
		}
		return $fetched;
	}

	public function fetch($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->fetchPreparedSql($this->prepareSql($prepareSql, $replacers));
	}

	public function fetchRow($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->fetchPreparedSql($this->prepareSql($prepareSql, $replacers), false, true);
	}

	public function fetchColumn($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->fetchPreparedSql($this->prepareSql($prepareSql, $replacers), true, false);
	}

	public function fetchCell($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->fetchPreparedSql($this->prepareSql($prepareSql, $replacers), true, true);
	}

	/**************************************************************
	 QUOTERS
	 **************************************************************/
	
	public function quote($string, $withQuotes = true) {
		if (!is_scalar($string)) {
			throw new DaBase_Exception('Trying to quote "' . gettype($string) . '"');
		}
		if(is_bool($string)) {
			$string = $string ? 1 : 0;
		}
		return $withQuotes ? '\'' . mysql_real_escape_string($string, $this->dbLink) . '\'' : mysql_real_escape_string($string, $this->dbLink);
	}

	public function quoteArray(array $values, $withQuotes = true) {
		foreach ($values as &$value) {
			$value = $this->quote($value, $withQuotes);
		}
		return $values;
	}

	public function quoteName($name) {
		if (!is_scalar($name)) {
			throw new DaBase_Exception('Trying to quote "' . gettype($name) . '" as name');
		}
		if (!preg_match('/^[\d\w_]+$/', $name)) {
			throw new DaBase_Exception('Wrong name "' . $name . '" given to quote');
		}
		return '`' . $name . '`';
	}

	public function quoteNames(array $names) {
		foreach ($names as &$name) {
			$name = $this->quoteName($name);
		}
		return $names;
	}

	public function quoteEquals(array $fieldsValues) {
		$equals = array();
		foreach ($fieldsValues as $field => $value) {
			$equals[] = $this->quoteName($field) . '=' . $this->quote($value);
		}
		return $equals;
	}

	public function sql($sql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->prepareSql($sql, $replacers);
	}

	public function prepareSql($prepareSql, array $replacers) {
		$splitedSql = preg_split('/(' . implode('|', array_map('preg_quote', array(self::PRE_VALUES, self::PRE_VALUE, self::PRE_NAMES, self::PRE_NAME, self::PRE_EQUALS, self::PRE_AS_IS))) . ')/', $prepareSql, -1, PREG_SPLIT_DELIM_CAPTURE);
		if (count($replacers) != (count($splitedSql) - 1) / 2) {
			throw new DaBase_Exception('Count of replacers in prepare SQL "'.$prepareSql.'" mismatch');
		}
		if ($replacers) {
			$preparedSql = '';
			foreach ($splitedSql as $i => $p) {
				if ($i % 2) {
					$pos = ($i - 1) / 2;
					if ($p == self::PRE_VALUE || $p == self::PRE_NAME) {
						$p = $p == self::PRE_VALUE ? $this->quote($replacers[$pos]) : $this->quoteName($replacers[$pos]);
					}
					elseif ($p == self::PRE_VALUES || $p == self::PRE_NAMES) {
						$p = implode(', ', $p == self::PRE_VALUES ? $this->quoteArray($replacers[$pos]) : $this->quoteNames($replacers[$pos]));
					}
					elseif ($p == self::PRE_EQUALS) {
						$p = implode(', ', $this->quoteEquals($replacers[$pos]));
					}
					elseif ($p == self::PRE_AS_IS) {
						$p = $replacers[$pos];
					}
				}
				$preparedSql .= $p;
			}
			return $preparedSql;
		}
		else {
			return $prepareSql;
		}
	}

	/**************************************************************
	 CUTOM METHODS
	 **************************************************************/
	
	public function getLastInsertId() {
		return mysql_insert_id($this->dbLink);
	}
	
	public function getTables() {
		return $this->fetchColumn('SHOW TABLES');
	}
	
	public function getTableFields($tableName) {
		return $this->fetchColumn('SHOW FIELDS FROM #', $tableName);
	}

	public function setDebugCallback($callback) {
		if (!is_callable($callback)) {
			throw new DaBase_Exception('Debug callback is not callable');
		}
		$this->debugCallback = $callback;
	}
	
	protected function debugSql($sql) {
		if($this->debugCallback) {
			call_user_func($this->debugCallback, $sql);
		}
	}

	/**************************************************************
	 GETTERS MAGICS
	 **************************************************************/
	
	public function __get($getterName) {
		return $this->router->getGetterByName($getterName, $this);
	}

	public function __call($getterName, $idInArray) {
		return $this->router->getGetterByName($getterName, $this)->getObjectById(reset($idInArray));
	}

	/**************************************************************
	 TRANSACTIONS
	 **************************************************************/
	
	public function start() {
		$this->query('BEGIN');
		return $this;
	}

	public function commit() {
		$this->query('COMMIT');
		return $this;
	}

	public function rollback() {
		$this->query('ROLLBACK');
		return $this;
	}

	public function __destruct() {
		if ($this->dbLink) {
			if ($this->transactionStarted) {
				$this->rollback();
			}
			mysql_close($this->dbLink);
		}
	}
}
